package com.registration.web.utils;

import java.io.IOException;
import java.util.Map;
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.faces.lifecycle.Lifecycle;
import javax.faces.lifecycle.LifecycleFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.servlet.view.AbstractUrlBasedView;

/**
 * basado en el JsfView de spring-webflow
 */
public class JsfView extends AbstractUrlBasedView {

    private static final boolean JSF_12;
    private static final String JSF_12_METHOD = "getELContext";
    private Lifecycle facesLifecycle;

    static {
        if (ReflectionUtils.findMethod(FacesContext.class, JSF_12_METHOD) != null) {
            JSF_12 = true;
        } else {
            JSF_12 = false;
        }
    }

    public void afterPropertiesSet() throws Exception {
        super.afterPropertiesSet();
        facesLifecycle = createFacesLifecycle();
    }

    protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response)
            throws Exception {

        FacesContext facesContext = createFacesContext(request, response);

        populateRequestMap(facesContext, model);

        notifyBeforeListeners(PhaseId.RESTORE_VIEW, facesLifecycle, facesContext);

        ViewHandler viewHandler = facesContext.getApplication().getViewHandler();

        if (isAtLeastJsf12()) {
            viewHandler.initView(facesContext);
        }

        UIViewRoot viewRoot = viewHandler.createView(facesContext, getUrl());
        Assert.notNull(viewRoot, "A JSF view could not be created for " + getUrl());
        viewRoot.setLocale(RequestContextUtils.getLocale(request));
        viewRoot.setTransient(true);

        facesContext.setViewRoot(viewRoot);

        notifyAfterListeners(PhaseId.RESTORE_VIEW, facesLifecycle, facesContext);

        facesContext.setViewRoot(viewRoot);
        facesContext.renderResponse();
        try {
            notifyBeforeListeners(PhaseId.RENDER_RESPONSE, facesLifecycle, facesContext);
            logger.debug("Asking view handler to render view");
            facesContext.getApplication().getViewHandler().renderView(facesContext, viewRoot);
            notifyAfterListeners(PhaseId.RENDER_RESPONSE, facesLifecycle, facesContext);
        } catch (IOException e) {
            throw new FacesException("An I/O error occurred during view rendering", e);
        } finally {
            logger.debug("View rendering complete");
            facesContext.responseComplete();
            facesContext.release();
        }
    }

    private void populateRequestMap(FacesContext facesContext, Map<String, Object> model) {
        Map<String, Object> requestMap = facesContext.getExternalContext().getRequestMap();
        for (Map.Entry<String, Object> entry : model.entrySet()) {
            // JSF does not insist that putAll is implemented, hence we use individual put calls
            requestMap.put(entry.getKey(), entry.getValue());
        }
    }

    private FacesContext createFacesContext(HttpServletRequest request, HttpServletResponse response) {
        if (FacesContext.getCurrentInstance() != null) {
            return FacesContext.getCurrentInstance();
        }

        FacesContextFactory facesContextFactory = (FacesContextFactory) FactoryFinder
                .getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
        return facesContextFactory.getFacesContext(getServletContext(), request, response, facesLifecycle);
    }

    private Lifecycle createFacesLifecycle() {
        LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder
                .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
        return lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
    }

    public static boolean isAtLeastJsf12() {
        return JSF_12;
    }

    public static void notifyAfterListeners(PhaseId phaseId, Lifecycle lifecycle, FacesContext context) {
        PhaseEvent afterPhaseEvent = new PhaseEvent(context, phaseId, lifecycle);
        for (int i = 0; i < lifecycle.getPhaseListeners().length; i++) {
            PhaseListener listener = lifecycle.getPhaseListeners()[i];
            if (listener.getPhaseId() == phaseId || listener.getPhaseId() == PhaseId.ANY_PHASE) {
                listener.afterPhase(afterPhaseEvent);
            }
        }
    }

    public static void notifyBeforeListeners(PhaseId phaseId, Lifecycle lifecycle, FacesContext context) {
        PhaseEvent beforePhaseEvent = new PhaseEvent(context, phaseId, lifecycle);
        for (int i = 0; i < lifecycle.getPhaseListeners().length; i++) {
            PhaseListener listener = lifecycle.getPhaseListeners()[i];
            if (listener.getPhaseId() == phaseId || listener.getPhaseId() == PhaseId.ANY_PHASE) {
                listener.beforePhase(beforePhaseEvent);
            }
        }
    }
}
